package com.dynamic.mybatis.core.parser.field;

import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.visitor.SchemaStatVisitor;
import com.alibaba.druid.stat.TableStat;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.dynamic.mybatis.core.enums.Type;
import com.dynamic.mybatis.core.enums.TypeAlias;
import com.dynamic.mybatis.core.metadata.*;
import com.dynamic.mybatis.core.parser.xml.DynamicBaseMappedStatementParser;
import com.dynamic.mybatis.core.session.DynamicSqlSession;
import com.dynamic.mybatis.core.toolkit.BeanUtils;
import com.dynamic.mybatis.core.toolkit.StringUtils;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.session.Configuration;

import java.sql.*;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description
 * @Author xs
 * @Date 2022/11/17 11:05
 */
public abstract class DynamicMappedStatementDataBaseFieldParser extends DatabaseMetaDataHelper{

    public   Configuration configuration;
    protected DynamicSqlSession dynamicSqlSession;

    public DynamicMappedStatementDataBaseFieldParser(DynamicSqlSession dynamicSqlSession) {
        this.dynamicSqlSession = dynamicSqlSession;
        this.configuration = dynamicSqlSession.getSqlSession().getConfiguration();
    }

    public Configuration getConfiguration() {
        return configuration;
    }

    public Map<String,Map<String,List<String>>> queryTableColumn(Map<String,Connection> connections){
        Map<String,Map<String,List<String>>> dataSouces = new HashMap<>();
        for(Map.Entry<String,Connection> entry : connections.entrySet()){
            dataSouces.put(entry.getKey(),queryTableColumn(entry.getKey(),entry.getValue()));
        }
        return dataSouces;
    }

    public Map<String,List<String>> queryTableColumn(String dbName,Connection connection){
        Map<String,List<String>> tables = new HashMap<>();
        try {
            DatabaseMetaData dbMetaData = connection.getMetaData();
            ResultSet tableResultSet =  dbMetaData.getTables(dbName,null,null,new String[]{"TABLE"});
            while (tableResultSet.next()){
                String tableName = tableResultSet.getString("TABLE_NAME");
                ResultSet columnResultSet = dbMetaData.getColumns(dbName, null, tableName, null);
                while (columnResultSet.next()){
                    String columnName = columnResultSet.getString("COLUMN_NAME");
                    List<String> columnNames =  tables.get(tableName);
                    if(columnNames == null){
                        columnNames = new ArrayList<>();
                        tables.put(tableName,columnNames);
                    }
                    columnNames.add(columnName);
                }
                columnResultSet.close();
            }
            tableResultSet.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return tables;
    }

    protected List<MappedStatementMetaField>   getDataBaseFieldUpdateMappedStatementField(DynamicSqlSession dynamicSqlSession,DynamicMappedStatement dms,Object parameter){
        List<MappedStatementTable>  tables =   getDataBaseFieldUpdateMappedStatement(dynamicSqlSession,dms,parameter);
        if(CollectionUtils.isEmpty(tables)){
            return Collections.EMPTY_LIST;
        }
        return tables.stream().flatMap(t -> t.getFields().stream()).collect(Collectors.toList());
    }





    protected List<MappedStatementTable>   getDataBaseFieldUpdateMappedStatement(DynamicSqlSession dynamicSqlSession,DynamicMappedStatement dms,Object parameter){
        MappedStatement ms =  dynamicSqlSession.getSqlSession().getConfiguration().getMappedStatement(dms.getId());
        String sql = ms.getSqlSource().getBoundSql(parameter).getSql();
        String urlSplit[];
        try (Connection connection = dynamicSqlSession.getConnection(dms.getId());){
            urlSplit =   connection.getMetaData().getURL().split(":");
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        List<SQLStatement>   sqlStatements = SQLUtils.parseStatements(sql, urlSplit[1]);
        Collection<TableStat.Column> tableStatColumns = null;
        for(SQLStatement sqlStatement : sqlStatements){
              SchemaStatVisitor visitor = SQLUtils.createSchemaStatVisitor(urlSplit[1]);
              sqlStatement.accept(visitor);
              tableStatColumns = visitor.getColumns();
        }

        Set<String> tables = tableStatColumns.stream().map(TableStat.Column::getTable).collect(Collectors.toSet());
        Set<String> columns = tableStatColumns.stream().map(TableStat.Column::getName).collect(Collectors.toSet());
        if(tables != null && tables.size() > 0 && columns != null && columns.size() > 0){
           return new DatabaseMetaDataHelper().getDatabaseMeta(dynamicSqlSession,(String)dms.getTargetDataSourceKey(),tables.toArray(new String[tables.size()]));
        }

        return null;
    }



    protected List<MappedStatementMetaField>  getDataBaseFieldQueryMappedStatement(DynamicSqlSession dynamicSqlSession, DynamicMappedStatement dms, Object parameter){

        DynamicConfiguration configuration = (DynamicConfiguration)getConfiguration();
        MappedStatement mappedStatement = configuration.getMappedStatement(dms.getId());
        if(mappedStatement == null ){
            return Collections.EMPTY_LIST;
        }
        List<MappedStatementMetaField> dbFields;
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        String sql = boundSql.getSql();
        List<ParameterMapping> parameterMapping = boundSql.getParameterMappings();
        PreparedStatement ps;
        Statement stmt;
        ResultSet rs;
        try (Connection connection = dynamicSqlSession.getConnection(dms.getId())){
            if(CollectionUtils.isNotEmpty(parameterMapping)){
                ps =   connection.prepareStatement(sql);
                configuration.newParameterHandler(mappedStatement, parameter, boundSql).setParameters(ps);
                rs =   ps.executeQuery();
            }else {
                stmt = connection.createStatement();
                rs = stmt.executeQuery(sql);
            }
            DatabaseMetaData dbMetaData = connection.getMetaData();
            ResultSetMetaData rsMetaData = rs.getMetaData();
            //获取字段信息
            dbFields = getDataBaseField(dbMetaData,rsMetaData);
        }catch (Exception e){
            throw  new RuntimeException(e);
        }
        return dbFields;
    }

    private List<MappedStatementMetaField> getDataBaseField(DatabaseMetaData dbMetaData,ResultSetMetaData rsMetaData) throws SQLException {

        List<MappedStatementMetaField> fields = new ArrayList<>();
        List<Map<String,Object>> printFields  = new ArrayList<>();
        for (int i = 1, columnCount = rsMetaData.getColumnCount(); i <= columnCount; i++) {
            String remark = null;
            ResultSet results;
            if(StringUtils.isNotBlank(rsMetaData.getCatalogName(i)) && StringUtils.isNotBlank(rsMetaData.getTableName(i))
                    &&  (results = dbMetaData.getColumns(rsMetaData.getCatalogName(i), rsMetaData.getSchemaName(i), rsMetaData.getTableName(i), rsMetaData.getColumnName(i))) != null
                    && !results.isLast()
            ){
                while (results.next())  remark = results.getString("REMARKS");
            }

            MappedStatementMetaField field  = new MappedStatementMetaField();
            field.setDataBase(rsMetaData.getCatalogName(i));
            field.setSchema(rsMetaData.getSchemaName(i));
            field.setTableName(rsMetaData.getTableName(i));
            field.setColumn(rsMetaData.getColumnLabel(i));
            field.setField(StringUtils.underlineToCamel(rsMetaData.getColumnLabel(i)));
            field.setVisible(Boolean.TRUE);
            field.setName((StringUtils.isNotBlank(remark) ? remark : field.getField()));
            field.setJavaTypeClass(TypeAlias.getType(rsMetaData.getColumnClassName(i)));
            field.setJavaType(rsMetaData.getColumnClassName(i));
            JDBCType jdbcType = JDBCType.valueOf(rsMetaData.getColumnType(i));
            Type type = Type.getType(jdbcType);

            field.setType(type.getType());
            field.setJdbcType(JDBCType.valueOf(rsMetaData.getColumnType(i)));
            MappedStatementFieldValidator validator = new MappedStatementFieldValidator();
            validator.setRequired(rsMetaData.isNullable(i) == 1);
            validator.setMin(type.getMin());
            validator.setMax(type.getMax());
            if(Type.StandardType.VARCHAR.type.equals(field.getType())){
                validator.setMaxLength(rsMetaData.getPrecision(i));
            }else {
                validator.setMaxLength(type.getLength());
            }
            if(field.getJdbcType() == JDBCType.TIMESTAMP){
                validator.setDatePattern("yyyy-MM-dd HH:mm:ss");
            }else if(field.getJdbcType() == JDBCType.DATE){
                validator.setDatePattern("yyyy-MM-dd");
            }else if(field.getJdbcType() == JDBCType.TIME){
                validator.setDatePattern("HH:mm:ss");
            }
            field.setValidator(validator);
            field.setSort((byte)i);

            Map<String,Object> value = new LinkedHashMap<>();
            value.put("catalogName",rsMetaData.getCatalogName(i));
            value.put("tableName",rsMetaData.getTableName(i));
            value.put("columnName",rsMetaData.getColumnName(i));
            value.put("columnLabel",rsMetaData.getColumnLabel(i));
            value.put("columnType",rsMetaData.getColumnType(i));
            value.put("columnTypeName",rsMetaData.getColumnTypeName(i));
            value.put("columnClassName",rsMetaData.getColumnClassName(i));
            value.put("precision",rsMetaData.getPrecision(i));
            value.put("columnDisplaySize",rsMetaData.getColumnDisplaySize(i));
            value.put("isNullable",rsMetaData.isNullable(i));
            value.put("schemaName",rsMetaData.getSchemaName(i));
            value.put("REMARKS",remark);
            printFields.add(value);
            fields.add(field);
        }
        //获取主键
        setPrimaryKey(dbMetaData,fields);
        //格式化输出字段信息
        printFieldPropertes(printFields);
        return  fields;
    }


    //设置表主键
    private void setPrimaryKey(DatabaseMetaData dbMetaData,List<MappedStatementMetaField> fields) throws SQLException {
        if(CollectionUtils.isEmpty(fields)){  return;   }

        Map<String,MappedStatementField> map = fields.stream().collect(Collectors.toMap(f-> f.getDataBase()+f.getSchema()+f.getTableName(), f->f,(f1, f2)->f2));
        for(MappedStatementField field : map.values()){
            ResultSet resultSet =  dbMetaData.getPrimaryKeys(field.getDataBase(),field.getSchema(),field.getTableName());
            while(resultSet.next()) {
                String  primaryKeyColumn= resultSet.getString("COLUMN_NAME");
                MappedStatementField pkFieldVo =   fields.stream().filter(f ->
                        Objects.equals(f.getDataBase(),field.getDataBase())
                                && Objects.equals(f.getSchema(),field.getSchema())
                                && Objects.equals(f.getTableName(),field.getTableName())
                                && Objects.equals(f.getField(),primaryKeyColumn)
                ).findFirst().orElse(null);
                if(pkFieldVo != null ){
                    pkFieldVo.setPrimaryKey(Boolean.TRUE);
                }
            }
        }
    }

    //将db获取的字段数据合并到java字段数据中
    protected   List<MappedStatementMetaField> mergeDataBseToJavaBatch(List<MappedStatementMetaField> dbFields ,List<MappedStatementMetaField> javaFields) {
          return mergeDataBseToJavaBatch(dbFields,javaFields,Boolean.FALSE);
    }

    protected   List<MappedStatementMetaField> mergeDataBseToJavaBatch(List<MappedStatementMetaField> dbFields ,List<MappedStatementMetaField> javaFields,boolean ignoreMergeRequired) {
        if(CollectionUtils.isEmpty(javaFields)){
            return dbFields;
        }
        if(CollectionUtils.isEmpty(dbFields)){
            return javaFields;
        }
        for(MappedStatementMetaField dbField : dbFields){
            mergeToJavaBatchRecursion(dbField,javaFields, ignoreMergeRequired);
        }
        return  javaFields;
    }

    private void mergeToJavaBatchRecursion(MappedStatementMetaField dbField ,List<MappedStatementMetaField> javaFields,boolean ignoreMergeRequired){
        for(MappedStatementMetaField javaField : javaFields){
            if((StringUtils.isBlank(javaField.getTableName()) || Objects.equals(dbField.getTableName(),javaField.getTableName())) && dbField.getField().equals(javaField.getField())){
                boolean required = javaField.getValidator() != null ? javaField.getValidator().isRequired() : false;
                BeanUtils.copy(dbField,javaField);
                if(ignoreMergeRequired){
                  javaField.getValidator().setRequired(required);
                }
            }
            if(CollectionUtils.isNotEmpty(javaField.getChildren())){
                mergeToJavaBatchRecursion(dbField,javaField.getChildren(),ignoreMergeRequired);
            }
        }
    }


    public static void printFieldPropertes(List<Map<String,Object>> fieldPropertes) {
        System.out.println();
        System.out.println("-------------------------------------------------------------------------------------------------------");
        List<Integer> maxLength = new ArrayList<>();
        int fixed = 4;
        for(Map<String,Object> propertes : fieldPropertes){
            int index = 0;
            for(Map.Entry<String,Object> entry : propertes.entrySet()) {
                int keyLength = String.valueOf(entry.getKey()).length();
                int valuelength = String.valueOf(entry.getValue()).length();
                if(maxLength.size() <= index){
                    int max = keyLength > valuelength ? keyLength: valuelength;
                    maxLength.add(max);
                }
                else {
                    int a = maxLength.get(index) > keyLength ? maxLength.get(index) : keyLength;
                    int max = a > valuelength ? a : valuelength;
                    maxLength.set(index,max);
                }
                index++;
            }
        }
        maxLength  = maxLength.stream().map(item -> item + fixed).collect(Collectors.toList());
        int index = 0;
        for(String key : fieldPropertes.get(0).keySet()){
            System.out.print(String.format("%-"+maxLength.get(index)+"s",key));
            index++;
        }
        System.out.println();
        for( Map<String,Object> propertes: fieldPropertes){
            index= 0;
            for(Map.Entry<String,Object> entry : propertes.entrySet()) {
                System.out.print(String.format("%-"+maxLength.get(index)+"s",entry.getValue()));
                index++;
            }
            System.out.println();
        }
        System.out.println("-------------------------------------------------------------------------------------------------------");
        System.out.println();
    }

}